package redis

import (
	"gitee.com/shibingli/goredis/internal/redis_sentinel"
	redisgo "github.com/gomodule/redigo/redis"
	"github.com/mna/redisc"
	"strings"
	"time"
)

const (
	RunModeStandard = "standard"
	RunModeCluster  = "cluster"
	RunModeSentinel = "sentinel"
)

var (
	DefaultPoolOption = &PoolOption{
		MaxIdle:     5,
		MaxActive:   20,
		IdleTimeout: time.Second * 30,
		DialOption:  []redisgo.DialOption{},
	}
)

type Redis struct {
	Nodes        []string
	RunMode      string
	Pool         *redisgo.Pool
	ClusterPool  *redisc.Cluster
	SentinelPool *redisgo.Pool

	poolOptions *PoolOption
}

func (r *Redis) createPool(addr string, opts ...redisgo.DialOption) (*redisgo.Pool, error) {
	if len(opts) > 0 {
		r.poolOptions.DialOption = opts
	}
	return newPool(addr, r.poolOptions)
}

func (r *Redis) Close() error {
	switch r.RunMode {
	case RunModeStandard:
		return r.Pool.Close()
	case RunModeSentinel:
		return r.SentinelPool.Close()
	case RunModeCluster:
		return r.ClusterPool.Close()
	}

	return nil
}

type PoolOption struct {
	MaxIdle     int
	MaxActive   int
	IdleTimeout time.Duration
	DialOption  []redisgo.DialOption
}

func Dial(addr string, opts ...*PoolOption) (*Redis, error) {

	opt := DefaultPoolOption
	if len(opts) > 0 {
		opt = opts[0]
	}

	redis := &Redis{
		RunMode:     RunModeStandard,
		poolOptions: opt,
	}

	pool, err := newPool(addr, opt)
	if err != nil {
		return nil, err
	}

	redis.Pool = pool

	return redis, nil
}

func DialCluster(nodes []string, opts ...*PoolOption) (*Redis, error) {

	opt := DefaultPoolOption
	if len(opts) > 0 {
		opt = opts[0]
	}

	redis := &Redis{
		RunMode:     RunModeCluster,
		poolOptions: opt,
	}

	cluster := &redisc.Cluster{
		StartupNodes: nodes,
		DialOptions:  opt.DialOption,
		CreatePool:   redis.createPool,
	}

	if err := cluster.Refresh(); err != nil {
		return nil, err
	}

	redis.ClusterPool = cluster

	return redis, nil
}

func DialSentinel(nodes []string, masterName string, opts ...*PoolOption) (*Redis, error) {
	masterName = strings.TrimSpace(masterName)
	if masterName == "" {
		masterName = "mymaster"
	}

	opt := DefaultPoolOption
	if len(opts) > 0 {
		opt = opts[0]
	}

	redis := &Redis{
		RunMode:     RunModeSentinel,
		poolOptions: opt,
	}

	stl := &redis_sentinel.Sentinel{
		Addrs:      nodes,
		MasterName: masterName,
		Dial: func(addr string) (redisgo.Conn, error) {
			c, err := redisgo.Dial("tcp", addr, opt.DialOption...)
			if err != nil {
				return nil, err
			}
			return c, nil
		},
		Pool: redis.createPool,
	}

	masterAddr, err := stl.MasterAddr()
	if err != nil {
		return nil, err
	}

	pool, err := redis.createPool(masterAddr, opt.DialOption...)
	if err != nil {
		return nil, err
	}
	redis.SentinelPool = pool

	return redis, nil
}

func newPool(addr string, opts *PoolOption) (*redisgo.Pool, error) {
	pool := &redisgo.Pool{
		MaxIdle:     opts.MaxIdle,
		MaxActive:   opts.MaxActive,
		IdleTimeout: opts.IdleTimeout,
		Dial: func() (redisgo.Conn, error) {
			return redisgo.Dial("tcp", addr, opts.DialOption...)
		},
		TestOnBorrow: func(c redisgo.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}
	return pool, nil
}
